using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Gko.Utils;
using Gko.Wpf.Mvvm.Interfaces;

namespace Gko.Wpf.Mvvm
{
	public class Mediator<TMessageId> : MediatorBase<TMessageId>, IMediator<TMessageId>
		where TMessageId : IEquatable<TMessageId>
	{
		public Mediator(MediatorOptions options)
			: base(options)
		{
		}

		public Mediator()
		{
		}

		#region IMediator<TMessageId> Members

		/// <exception cref="ArgumentNullException">One of arguments is null.</exception>
		public void Register<TValue>(TMessageId messageId, Action<TValue> action)
		{
			if (ReferenceEquals(messageId, null)) {
				throw new ArgumentNullException("messageId");
			}
			if (action == null) {
				throw new ArgumentNullException("action");
			}
			this.Add(messageId, action);
		}

		/// <exception cref="ArgumentNullException"><c>messageId</c> is null.</exception>
		public void Notify<TValue>(TMessageId messageId, TValue value)
		{
			this.Notify(messageId, value, this.Options);
		}

		/// <exception cref="ArgumentNullException"><c>messageId</c> is null.</exception>
		public void Notify<TValue>(TMessageId messageId, TValue value, MediatorOptions options)
		{
			if (ReferenceEquals(messageId, null)) {
				throw new ArgumentNullException("messageId");
			}
			this.CallActions(this.GetActions<TValue>(messageId), value, options);
			this.SetValue(messageId, value);
		}

		/// <exception cref="ArgumentNullException"><c>messageId</c> is null.</exception>
		public void NotifyAsync<TValue>(TMessageId messageId, TValue value)
		{
			if (ReferenceEquals(messageId, null)) {
				throw new ArgumentNullException("messageId");
			}
			this.DoCallActionsInBackground(this.GetActions<TValue>(messageId), value, this.Options);
			this.SetValue(messageId, value);
		}

		/// <exception cref="ArgumentNullException"><c>messageId</c> is null.</exception>
		public void Notify<TValue>(TMessageId messageId, TValue value, SynchronizationContext context)
		{
			this.Notify(messageId, value, context, this.Options);
		}

		/// <exception cref="ArgumentNullException"><c>messageId</c> is null.</exception>
		public void Notify<TValue>(TMessageId messageId, TValue value, SynchronizationContext context, MediatorOptions options)
		{
			if (ReferenceEquals(messageId, null)) {
				throw new ArgumentNullException("messageId");
			}
			this.CallActions(this.GetActions<TValue>(messageId), value, context, options);
			this.SetValue(messageId, value);
		}

		/// <exception cref="ArgumentNullException"><c>messageId</c> is null.</exception>
		public void NotifyAsync<TValue>(TMessageId messageId, TValue value, SynchronizationContext context)
		{
			if (ReferenceEquals(messageId, null)) {
				throw new ArgumentNullException("messageId");
			}
			this.DoCallActionsInBackground(this.GetActions<TValue>(messageId), value, context, this.Options);
			this.SetValue(messageId, value);
		}

		public TValue GetLastNotified<TValue>(TMessageId messageId, TValue defaultValue)
		{
			return this.GetLastNotifiedValue(messageId, defaultValue);
		}

		#endregion
	}

	public class Mediator<TMessageId, TSenderId> : MediatorBase<Mediator<TMessageId, TSenderId>.MultiKey>,
	                                               IMediator<TMessageId, TSenderId>
		where TMessageId : IEquatable<TMessageId>
		where TSenderId : IEquatable<TSenderId>
	{
		public Mediator()
		{
		}

		public Mediator(MediatorOptions options)
			: base(options)
		{
		}

		#region IMediator<TMessageId,TSenderId> Members

		/// <exception cref="ArgumentNullException">One of arguments is null.</exception>
		public void Register<TValue>(TMessageId messageId, TSenderId senderId, Action<TValue> action)
		{
			if (ReferenceEquals(messageId, null)) {
				throw new ArgumentNullException("messageId");
			}
			if (ReferenceEquals(senderId, null)) {
				throw new ArgumentNullException("senderId");
			}
			if (action == null) {
				throw new ArgumentNullException("action");
			}
			this.Add(new MultiKey(messageId, senderId), action);
		}

		/// <exception cref="ArgumentNullException"><c>messageId</c> or <c>senderId</c> is null.</exception>
		public void Notify<TValue>(TMessageId messageId, TSenderId senderId, TValue value)
		{
			this.Notify(messageId, senderId, value, this.Options);
		}

		/// <exception cref="ArgumentNullException"><c>messageId</c> or <c>senderId</c> is null.</exception>
		public void Notify<TValue>(TMessageId messageId, TSenderId senderId, TValue value, MediatorOptions options)
		{
			if (ReferenceEquals(messageId, null)) {
				throw new ArgumentNullException("messageId");
			}
			if (ReferenceEquals(senderId, null)) {
				throw new ArgumentNullException("senderId");
			}
			this.CallActions(this.GetActions<TValue>(messageId, senderId), value, options);
			this.SetValue(new MultiKey(messageId, senderId), value);
		}

		/// <exception cref="ArgumentNullException"><c>messageId</c> or <c>senderId</c> is null.</exception>
		public void NotifyAsync<TValue>(TMessageId messageId, TSenderId senderId, TValue value)
		{
			if (ReferenceEquals(messageId, null)) {
				throw new ArgumentNullException("messageId");
			}
			if (ReferenceEquals(senderId, null)) {
				throw new ArgumentNullException("senderId");
			}
			this.DoCallActionsInBackground(this.GetActions<TValue>(messageId, senderId), value, this.Options);
			this.SetValue(new MultiKey(messageId, senderId), value);
		}

		/// <exception cref="ArgumentNullException"><c>messageId</c> or <c>senderId</c> is null.</exception>
		public void Notify<TValue>(TMessageId messageId, TSenderId senderId, TValue value, SynchronizationContext context)
		{
			this.Notify(messageId, senderId, value, context, this.Options);
		}

		/// <exception cref="ArgumentNullException"><c>messageId</c> or <c>senderId</c> is null.</exception>
		public void Notify<TValue>(TMessageId messageId, TSenderId senderId, TValue value, SynchronizationContext context, MediatorOptions options)
		{
			if (ReferenceEquals(messageId, null)) {
				throw new ArgumentNullException("messageId");
			}
			if (ReferenceEquals(senderId, null)) {
				throw new ArgumentNullException("senderId");
			}
			this.CallActions(this.GetActions<TValue>(messageId, senderId), value, context, options);
			this.SetValue(new MultiKey(messageId, senderId), value);
		}

		/// <exception cref="ArgumentNullException"><c>messageId</c> or <c>senderId</c> is null.</exception>
		public void NotifyAsync<TValue>(TMessageId messageId, TSenderId senderId, TValue value, SynchronizationContext context)
		{
			if (ReferenceEquals(messageId, null)) {
				throw new ArgumentNullException("messageId");
			}
			if (ReferenceEquals(senderId, null)) {
				throw new ArgumentNullException("senderId");
			}
			this.DoCallActionsInBackground(this.GetActions<TValue>(messageId, senderId), value, context, this.Options);
			this.SetValue(new MultiKey(messageId, senderId), value);
		}

		public TValue GetLastNotified<TValue>(TMessageId messageId, TSenderId senderId, TValue defaultValue)
		{
			return this.GetLastNotifiedValue(new MultiKey(messageId, senderId), defaultValue);
		}

		#endregion

		private IEnumerable<Action<TValue>> GetActions<TValue>(TMessageId messageId, TSenderId senderId)
		{
			return this.GetActions<TValue>(new MultiKey(messageId, senderId));
		}

		#region Nested type: MultiKey

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")]
		public struct MultiKey : IEquatable<MultiKey>
		{
			private readonly TMessageId messageId;
			private readonly TSenderId senderId;

			public MultiKey(TMessageId messageId, TSenderId senderId)
				: this()
			{
				this.messageId = messageId;
				this.senderId = senderId;
			}

			public TMessageId MessageId
			{
				get
				{
					return this.messageId;
				}
			}

			public TSenderId SenderId
			{
				get
				{
					return this.senderId;
				}
			}

			#region IEquatable<Mediator<TMessageId,TSenderId>.MultiKey> Members

			public bool Equals(MultiKey other)
			{
				return other.messageId.Equals(this.messageId) && other.senderId.Equals(this.senderId);
			}

			#endregion

			public override bool Equals(object obj)
			{
				if (ReferenceEquals(null, obj)) {
					return false;
				}
				return obj.GetType() == typeof(MultiKey) && this.Equals((MultiKey)obj);
			}

			public override int GetHashCode()
			{
				return HashCodeUtility.CombineHashCodes(ObjectUtility.GetHashCode(this.messageId),
				                                        ObjectUtility.GetHashCode(this.senderId));
			}

			public static bool operator ==(MultiKey left, MultiKey right)
			{
				return left.Equals(right);
			}

			public static bool operator !=(MultiKey left, MultiKey right)
			{
				return !left.Equals(right);
			}
		}

		#endregion
	}
}